﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using Bio.Data.Interfaces;
using LabelRNA;

namespace Bio.Data.Providers.Structure.RNAml
{
    /// <summary>
    /// This loads an rnaml-based structure diagram
    /// 
    /// Since we cannot store a fully annotated model in RNAml format were
    /// are only expecting a subset of the tags.
    /// </summary>
    class RNAMLFile : BioFileDataLoader<IStructureModelBioEntity>
    {
        private SimpleRNASequence _sequence;
        private Dictionary<int, int> _basePairs;
        private List<IBasePairBioEntity> _basePairEntities;
        private RNASecondaryStructureNucleotideTextShape _globalstructureModelNucleotideTextRendering;
        private RNASecondaryStructureNucleotideCircleShape _globalstructureModelNucleotideCircleRendering;
        private NucleotideMetadata.RenderingMode? _globalstructureModelNucleotideRenderingMode = null;
        private RNASecondaryStructureBasePairConnector _globalstructureModelBasePairConnectorRendering;
        private BasePairMetadata.ConnectorRenderingMode? _globalstructureModelBasePairConnectorRenderingMode;
        private bool? _globalstructureModelBasePairConnectorIsVisible;

        protected override bool OnInitialize()
        {
            if (base.OnInitialize())
            {
                using (var filestream = new FileStream(InitializationData, FileMode.Open))
                {
                    XDocument strmdlrnaml = XDocument.Load(filestream);
                    XmlSchemaSet rnamlschema = new XmlSchemaSet();

                    try
                    {
                        XmlReader schemaReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("Bio.Data.Providers.Structure.RNAml.rnaml.xml"));
                        rnamlschema.Add(null, schemaReader);
                    }
                    catch (Exception)
                    {
                        return false;
                    }

                    bool validationErrors = false;
                    Exception error;
                    strmdlrnaml.Validate(rnamlschema, (s, e) => { validationErrors = true; error = e.Exception; });

                    if (validationErrors)
                        return false;

                    XElement strmdl = strmdlrnaml.Element(RNAMLTags.RNAMLLabel);
                    XElement strmdlmolecule = strmdl.Element(RNAMLTags.RNAMLMoleculeLabel);

                    //1. We try to extract and load the sequence
                    if (!LoadSequence(strmdlmolecule))
                        return false;

                    //2. We try to extract out the structure model and rendering
                    XElement strmdlstructure = strmdlmolecule.Element(RNAMLTags.RNAMLStructureLabel);
                    if (strmdlstructure != null)
                    {
                        XElement strmdlstructuremodel = strmdlstructure.Element(RNAMLTags.RNAMLStructureModelLabel);
                        if (strmdlstructuremodel != null)
                            LoadStructureModel(strmdlstructuremodel);
                        
                    }

                    return true;
                }
            }
            return false;
        }

        protected override void OnLoad()
        {
            try
            {
                RNAParser labelRNAParser = new RNAParser(_basePairs, _sequence.SequenceData.Count);
                XElement extentsXML = labelRNAParser.ParseToExtents();
                var extents = from extent in extentsXML.Descendants("Extent")
                              select new RNASecondaryStructureExtent(_sequence,
                                                                   Int32.Parse(extent.Element("ExtentID").Value),
                                                                   Int32.Parse(extent.Element("ExtentOrdinal").Value),
                                                                   (StructureExtentType)Enum.Parse(typeof(StructureExtentType), extent.Element("ExtentTypeID").Value, true),
                                                                   Int32.Parse(extent.Element("ExtentStartIndex").Value),
                                                                   Int32.Parse(extent.Element("ExtentEndIndex").Value));

                RNASecondaryStructureModel model = new RNASecondaryStructureModel(InitializationData, _sequence)
                {
                    BasePairs = _basePairEntities.Cast<IBasePairBioEntity>().ToList(),
                    Extents = extents.Cast<IStructureExtentBioEntity>().ToList()
                };
                model.SetExtendedProperty(StructureModelMetadata.Key, new StructureModelMetadata());

                if (_globalstructureModelNucleotideRenderingMode != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideRenderingMode = _globalstructureModelNucleotideRenderingMode;

                if (_globalstructureModelNucleotideCircleRendering != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideCircleRendering = _globalstructureModelNucleotideCircleRendering;

                if (_globalstructureModelNucleotideTextRendering != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideTextRendering = _globalstructureModelNucleotideTextRendering;

                if (_globalstructureModelBasePairConnectorIsVisible != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorVisible = _globalstructureModelBasePairConnectorIsVisible;

                if (_globalstructureModelBasePairConnectorRenderingMode != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorStyle = _globalstructureModelBasePairConnectorRenderingMode;

                if (_globalstructureModelBasePairConnectorRendering != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorRendering = _globalstructureModelBasePairConnectorRendering;

                Entities.Add(model);
            }
            catch (Exception) //We ran into some error parsing the extents.
            {
                Entities.Add(new RNASecondaryStructureModel(InitializationData, _sequence)
                {
                    BasePairs = _basePairEntities.Cast<IBasePairBioEntity>().ToList()
                });
            }
        }

        private bool LoadSequence(XElement strmdlmolecule)
        {
            if (strmdlmolecule == null)
                return false;

            XElement rnamlsequenceelement = strmdlmolecule.Element(RNAMLTags.RNAMLSequenceLabel);
            if (rnamlsequenceelement == null)
                return false;
            if (rnamlsequenceelement.IsEmpty)
                return false;

            XElement rnamlsequencedata = rnamlsequenceelement.Element(RNAMLTags.RNAMLSequenceDataLabel);
            if(rnamlsequencedata == null)
                return false;
            if (rnamlsequencedata.IsEmpty)
                return false;
            _sequence = new SimpleRNASequence();
            foreach (var nt in rnamlsequencedata.Value)
                _sequence.AddSymbol(nt);

            return true;
        }

        private void LoadStructureModel(XElement strmdlstructuremodel)
        {
            if (strmdlstructuremodel == null)
                return;

            var formattingcomments = strmdlstructuremodel.Nodes().Where(elem => elem.NodeType == XmlNodeType.Comment).Cast<XComment>();
            if (formattingcomments.Count() > 0)
            {
                _globalstructureModelNucleotideTextRendering = RNASecondaryStructureNucleotideTextShape.FromRNAML(formattingcomments);
                _globalstructureModelNucleotideCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromRNAML(formattingcomments);

                foreach (var formattingcomment in formattingcomments)
                {
                    if (!string.IsNullOrEmpty(formattingcomment.Value) && formattingcomment.Value.StartsWith(RNAMLTags.StrMLExtension))
                    {
                        string[] fields = Regex.Split(formattingcomment.Value, "::");
                        if (fields.Length == 3 && fields[0].Equals(RNAMLTags.StrMLExtension))
                        {
                           if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel))
                           {
                                NucleotideMetadata.RenderingMode desiredRenderStyle;
                                if (Enum.TryParse<NucleotideMetadata.RenderingMode>(fields[2], out desiredRenderStyle))
                                    _globalstructureModelNucleotideRenderingMode = desiredRenderStyle;
                           }
                        }
                    }
                }

                _globalstructureModelBasePairConnectorRendering = RNASecondaryStructureBasePairConnector.FromRNAML(formattingcomments);

                foreach (var formattingcomment in formattingcomments)
                {
                    if (!string.IsNullOrEmpty(formattingcomment.Value) && formattingcomment.Value.StartsWith(RNAMLTags.StrMLExtension))
                    {
                        string[] fields = Regex.Split(formattingcomment.Value, "::");
                        if (fields.Length == 3 && fields[0].Equals(RNAMLTags.StrMLExtension))
                        {
                            if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelPairConnectorRenderingStyleLabel))
                            {
                                BasePairMetadata.ConnectorRenderingMode desiredRenderStyle;
                                if (Enum.TryParse<BasePairMetadata.ConnectorRenderingMode>(fields[2], out desiredRenderStyle))
                                    _globalstructureModelBasePairConnectorRenderingMode = desiredRenderStyle;
                            }
                            else if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelPairConnectorVisibleLabel))
                            {
                                bool connVisible;
                                if (bool.TryParse(fields[2], out connVisible))
                                    _globalstructureModelBasePairConnectorIsVisible = connVisible;
                            }
                        }
                    }
                }

            }

            XElement strmdlstructureannot = strmdlstructuremodel.Element(RNAMLTags.RNAMLStructureModelAnnotationLabel);
            if(strmdlstructureannot!=null)
                LoadPairs(strmdlstructureannot.Elements(RNAMLTags.RNAMLBasePairLabel));

            XElement strmdldisplay = strmdlstructuremodel.Element(RNAMLTags.RNAMLStructureModelDisplayLabel);
            if (strmdldisplay != null)
            {
                SequenceRendering rendering = LoadRendering(strmdldisplay.Elements(RNAMLTags.RNAMLStructureModelDisplayPositionLabel));
                if (rendering != null && rendering.RenderingMap.Count == _sequence.SequenceData.Count)
                {
                    SequenceMetadata seqMetadata = new SequenceMetadata();
                    _sequence.SetExtendedProperty(SequenceMetadata.Key, seqMetadata);
                    seqMetadata.Data.Add(SequenceRendering.Key, rendering);
                }
            }
            return;
        }

        private void LoadPairs(IEnumerable<XElement> basePairs)
        {
            if (basePairs == null)
                return;

            _basePairs = new Dictionary<int, int>();
            _basePairEntities = new List<IBasePairBioEntity>();
            foreach (var strmdlbp in basePairs)
            {
                int fivePrimeIdx = -1;
                int threePrimeIdx = -1;

                XElement fpidx = strmdlbp.Element(RNAMLTags.RNAMLBasePairFivePrimeLabel);
                if (fpidx != null && !string.IsNullOrEmpty(fpidx.Value))
                {
                    XElement fpbase = fpidx.Element(RNAMLTags.RNAMLBaseIDLabel);
                    if (fpbase != null && !string.IsNullOrEmpty(fpbase.Value))
                    {
                        XElement fpbaseval = fpbase.Element(RNAMLTags.RNAMLBasePositionLabel);
                        if (fpbaseval != null && !string.IsNullOrEmpty(fpbaseval.Value))
                            Int32.TryParse(fpbaseval.Value, out fivePrimeIdx);
                    }
                }
                
                XElement tpidx = strmdlbp.Element(RNAMLTags.RNAMLBasePairThreePrimeLabel);
                if (tpidx != null && !string.IsNullOrEmpty(tpidx.Value))
                {
                    XElement tpbase = tpidx.Element(RNAMLTags.RNAMLBaseIDLabel);
                    if (tpbase != null && !string.IsNullOrEmpty(tpbase.Value))
                    {
                        XElement tpbaseval = tpbase.Element(RNAMLTags.RNAMLBasePositionLabel);
                        if (tpbaseval != null && !string.IsNullOrEmpty(tpbaseval.Value))
                            Int32.TryParse(tpbaseval.Value, out threePrimeIdx);
                    }
                }

                if (fivePrimeIdx >= 1 && fivePrimeIdx <= _sequence.SequenceData.Count &&
                    threePrimeIdx >= 1 && threePrimeIdx <= _sequence.SequenceData.Count)
                {
                    if (fivePrimeIdx == threePrimeIdx) { continue; }
                    if (fivePrimeIdx < threePrimeIdx)
                    {
                        //We have to translate from the 1s-based file format
                        fivePrimeIdx = fivePrimeIdx - 1;
                        threePrimeIdx = threePrimeIdx - 1;
                        _basePairs.Add(fivePrimeIdx, threePrimeIdx);
                        RNASecondaryStructureBasePair bpObj = new RNASecondaryStructureBasePair(_sequence, fivePrimeIdx, threePrimeIdx);
                        BasePairMetadata bpMetadata = null;

                        var bpformattingcomments = strmdlbp.Nodes().Where(elem => elem.NodeType == XmlNodeType.Comment).Cast<XComment>();
                        if (bpformattingcomments.Count() > 0)
                        {
                            bpMetadata = new BasePairMetadata();
                            bpMetadata.ConnectorRendering = RNASecondaryStructureBasePairConnector.FromRNAML(bpformattingcomments);
                            
                            foreach (var bpformattingcomment in bpformattingcomments)
                            {
                                if (!string.IsNullOrEmpty(bpformattingcomment.Value) && bpformattingcomment.Value.StartsWith(RNAMLTags.StrMLExtension))
                                {
                                    string[] fields = Regex.Split(bpformattingcomment.Value, "::");
                                    if (fields.Length == 3 && fields[0].Equals(RNAMLTags.StrMLExtension))
                                    {
                                        if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelPairConnectorVisibleLabel))
                                        {
                                            bool connVisibility;
                                            if (bool.TryParse(fields[2], out connVisibility))
                                                bpMetadata.ConnectorVisible = connVisibility;
                                        }
                                        else if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelPairConnectorRenderingStyleLabel))
                                        {
                                            BasePairMetadata.ConnectorRenderingMode connRenderingStyle;
                                            if (Enum.TryParse<BasePairMetadata.ConnectorRenderingMode>(fields[2], out connRenderingStyle))
                                                bpMetadata.ConnectorStyle = connRenderingStyle;
                                        }
                                        else if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelNucleotideVisibilityLabel))
                                        {
                                            if (bpMetadata.NucleotideRenderingProperties == null)
                                                bpMetadata.NucleotideRenderingProperties = new NucleotideMetadata();

                                            bool ntVisibility;
                                            if (bool.TryParse(fields[2], out ntVisibility))
                                                bpMetadata.NucleotideRenderingProperties.Visible = ntVisibility;
                                        }
                                        else if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelNucleotideSubstitutionLabel))
                                        {
                                            if (bpMetadata.NucleotideRenderingProperties == null)
                                                bpMetadata.NucleotideRenderingProperties = new NucleotideMetadata();

                                            bpMetadata.NucleotideRenderingProperties.IsSubstituted = true;
                                            if (!string.IsNullOrEmpty(fields[2]) && fields[2].Length >= 1)
                                                bpMetadata.NucleotideRenderingProperties.SubstitutionCharacter = fields[2][0];
                                            else
                                                bpMetadata.NucleotideRenderingProperties.SubstitutionCharacter = ' ';
                                        }
                                        else if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel))
                                        {
                                            if (bpMetadata.NucleotideRenderingProperties == null)
                                                bpMetadata.NucleotideRenderingProperties = new NucleotideMetadata();

                                            NucleotideMetadata.RenderingMode desiredRenderStyle;
                                            if (Enum.TryParse(fields[2], out desiredRenderStyle))
                                                bpMetadata.NucleotideRenderingProperties.Style = desiredRenderStyle;
                                        }
                                    }
                                }
                            }

                            RNASecondaryStructureNucleotideTextShape ntTextRendering = RNASecondaryStructureNucleotideTextShape.FromRNAML(bpformattingcomments);
                            RNASecondaryStructureNucleotideCircleShape ntCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromRNAML(bpformattingcomments);

                            if (ntCircleRendering != null)
                            {
                                if (bpMetadata.NucleotideRenderingProperties == null)
                                    bpMetadata.NucleotideRenderingProperties = new NucleotideMetadata();
                                bpMetadata.NucleotideRenderingProperties.CircleRendering = ntCircleRendering;
                            }

                            if (ntTextRendering != null)
                            {
                                if (bpMetadata.NucleotideRenderingProperties == null)
                                    bpMetadata.NucleotideRenderingProperties = new NucleotideMetadata();
                                bpMetadata.NucleotideRenderingProperties.TextRendering = ntTextRendering;
                            }
                        }

                        if (bpMetadata != null)
                            bpObj.SetExtendedProperty(BasePairMetadata.Key, bpMetadata);

                        _basePairEntities.Add(bpObj);
                    }
                }
            }
        }

        private SequenceRendering LoadRendering(IEnumerable<XElement> coordinates)
        {
            if (coordinates == null)
                return null;

            //1. Initialize the empty rendering map.
            SequenceRendering rendering = new SequenceRendering();
            for (int i = 0; i < _sequence.SequenceData.Count; i++)
            {
                rendering.RenderingMap.Add(i, null);
            }

            //2. Load the renderings we have.
            foreach (var ntposition in coordinates)
            {
                XElement baseid = ntposition.Element(RNAMLTags.RNAMLBaseIDLabel);
                XElement basepos = ntposition.Element(RNAMLTags.RNAMLBaseCooridinatesLabel);

                if (baseid != null && !string.IsNullOrEmpty(baseid.Value) &&
                    basepos != null && !string.IsNullOrEmpty(basepos.Value))
                {
                    XElement baseidx = baseid.Element(RNAMLTags.RNAMLBasePositionLabel);
                    if (baseidx != null && !string.IsNullOrEmpty(baseidx.Value))
                    {
                        int ntIdx = -1;
                        if (int.TryParse(baseidx.Value, out ntIdx) && rendering.RenderingMap.ContainsKey((ntIdx - 1)))
                        {
                            ntIdx = ntIdx - 1; //Correct for 1s based indexing.
                            string[] coords = Regex.Split(basepos.Value, " ");
                            if (coords.Length == 2)
                            {
                                double xpos;
                                double ypos;
                                if (double.TryParse(coords[0], out xpos) && double.TryParse(coords[1], out ypos))
                                {
                                    RNASecondaryStructureNucleotideVector ntVector = new RNASecondaryStructureNucleotideVector()
                                    {
                                        NtIndex = ntIdx,
                                        StartX = xpos,
                                        StartY = ypos
                                    };
                                    rendering.RenderingMap[ntIdx] = ntVector;
                                }
                            }

                            NucleotideMetadata ntMetadata = new NucleotideMetadata();
                            ntMetadata.IsSubstituted = false;
                            var ntformattingcomments = baseid.Nodes().Where(elem => elem.NodeType == XmlNodeType.Comment).Cast<XComment>();
                            if (ntformattingcomments.Count() > 0)
                            {
                                RNASecondaryStructureNucleotideTextShape ntTextRendering = RNASecondaryStructureNucleotideTextShape.FromRNAML(ntformattingcomments);
                                RNASecondaryStructureNucleotideCircleShape ntCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromRNAML(ntformattingcomments);

                                foreach (var ntformattingcomment in ntformattingcomments)
                                {
                                    if (!string.IsNullOrEmpty(ntformattingcomment.Value) && ntformattingcomment.Value.StartsWith(RNAMLTags.StrMLExtension))
                                    {
                                        string[] fields = Regex.Split(ntformattingcomment.Value, "::");
                                        if (fields.Length == 3 && fields[0].Equals(RNAMLTags.StrMLExtension))
                                        {
                                            if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelNucleotideVisibilityLabel))
                                            {
                                                bool ntVisibility;
                                                if (bool.TryParse(fields[2], out ntVisibility))
                                                    ntMetadata.Visible = ntVisibility;
                                            }
                                            else if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelNucleotideSubstitutionLabel))
                                            {
                                                ntMetadata.IsSubstituted = true;
                                                if (!string.IsNullOrEmpty(fields[2]) && fields[2].Length >= 1)
                                                    ntMetadata.SubstitutionCharacter = fields[2][0];
                                                else
                                                    ntMetadata.SubstitutionCharacter = ' ';
                                            }
                                            else if (fields[1].Equals(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel))
                                            {
                                                NucleotideMetadata.RenderingMode desiredRenderStyle;
                                                if (Enum.TryParse(fields[2], out desiredRenderStyle))
                                                    ntMetadata.Style = desiredRenderStyle;
                                            }
                                        }
                                    }
                                }

                                ntMetadata.CircleRendering = ntCircleRendering;
                                ntMetadata.TextRendering = ntTextRendering;
                            }

                            IExtensibleProperties ntProps = _sequence.SequenceData[ntIdx] as IExtensibleProperties;
                            if (ntProps != null && ntMetadata != null)
                            {
                                ntProps.SetExtendedProperty(NucleotideMetadata.Key, ntMetadata);
                            }
                        }
                    }
                }
            }

            //3. Now fill in any holes.
            List<int> unmappedNtIdx = new List<int>();
            foreach (var key in rendering.RenderingMap.Keys.OrderBy(k => k))
            {
                if (rendering.RenderingMap[key] == null)
                    unmappedNtIdx.Add(key);
                else
                {
                    //We hit a group.
                    PositionUnmappedNucleotides(key, unmappedNtIdx, rendering);
                    unmappedNtIdx.Clear();
                }
            }

            if (unmappedNtIdx.Count > 0)
            {
                int anchorPos = rendering.RenderingMap.Last(kvp => kvp.Value != null).Key;
                PositionUnmappedNucleotides(anchorPos, unmappedNtIdx, rendering);
                unmappedNtIdx.Clear();
            }

            //4. Recompute all the rendering vectors
            var allNt = rendering.RenderingMap.Keys.OrderBy(k => k);
            foreach (var nt in allNt)
            {
                int nextNt = (nt + 1);
                if (nextNt < rendering.RenderingMap.Keys.Count)
                {
                    IVector ntVector = new RNASecondaryStructureNucleotideVector(rendering.RenderingMap[nt].StartX, rendering.RenderingMap[nt].StartY,
                        rendering.RenderingMap[nextNt].StartX, rendering.RenderingMap[nextNt].StartY);
                    rendering.RenderingMap[nt] = ntVector;
                }
            }
            return rendering;
        }

        private void PositionUnmappedNucleotides(int firstNtInGroupIdx, List<int> unmappedNtIdx, SequenceRendering sequenceRendering)
        {
            if (unmappedNtIdx != null && unmappedNtIdx.Count > 0)
            {
                //We will arrange the nt next to the first group member in a grid.
                if (firstNtInGroupIdx != -1)
                    sequenceRendering.RenderingMap[unmappedNtIdx.First()] = new RNASecondaryStructureNucleotideVector(sequenceRendering.RenderingMap[firstNtInGroupIdx].StartX + DEFAULT_NT_SPACING, sequenceRendering.RenderingMap[firstNtInGroupIdx].StartY);

                if (unmappedNtIdx.Count > 1)
                {
                    //Intermediate points.
                    int rowCount = 1;
                    double anchorXPos = sequenceRendering.RenderingMap[unmappedNtIdx.First()].StartX;
                    for (int i = 1; i < (unmappedNtIdx.Count - 1); i++)
                    {
                        if (rowCount % 5 == 0)
                        {
                            sequenceRendering.RenderingMap[unmappedNtIdx[i]] = new RNASecondaryStructureNucleotideVector(anchorXPos, sequenceRendering.RenderingMap[unmappedNtIdx[i] - 1].StartY + DEFAULT_NT_SPACING);
                        }
                        else
                        {
                            sequenceRendering.RenderingMap[unmappedNtIdx[i]] = new RNASecondaryStructureNucleotideVector(sequenceRendering.RenderingMap[unmappedNtIdx[i - 1]].StartX + DEFAULT_NT_SPACING, sequenceRendering.RenderingMap[unmappedNtIdx[i - 1]].StartY);
                        }

                        rowCount++;
                    }

                    //Now we will handle the last point, we'll put it to the right of the previous one
                    sequenceRendering.RenderingMap[unmappedNtIdx.Last()] = new RNASecondaryStructureNucleotideVector(sequenceRendering.RenderingMap[unmappedNtIdx.Last() - 1].StartX + DEFAULT_NT_SPACING, sequenceRendering.RenderingMap[unmappedNtIdx.Last() - 1].StartY);
                }
            }
            unmappedNtIdx.Clear();
        }

        private static double DEFAULT_NT_SPACING = 8.0;
    }
}
